package com.zyx.lock.service.impl;

import cn.hutool.core.lang.UUID;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zyx.lock.common.LockConstants;
import com.zyx.lock.factory.DistributedLockFactory;
import com.zyx.lock.mapper.StockMapper;
import com.zyx.lock.pojo.Stock;
import com.zyx.lock.service.IStockService;
import com.zyx.lock.utils.DistributedRedisLock;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Yaxi.Zhang
 * @since 2022/9/5 21:51
 */
@Service
@Slf4j
// 多例引发的jvm锁失效
// @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class StockServiceImpl extends ServiceImpl<StockMapper, Stock> implements IStockService {

    private final StringRedisTemplate redisTemplate;
    private final DistributedLockFactory distributedLockFactory;
    private final RedissonClient redissonClient;

    private final ReentrantLock jvmLock = new ReentrantLock();

    @Autowired
    public StockServiceImpl(StringRedisTemplate redisTemplate,
                            DistributedLockFactory distributedLockFactory,
                            RedissonClient redissonClient) {
        this.redisTemplate = redisTemplate;
        this.distributedLockFactory = distributedLockFactory;
        this.redissonClient = redissonClient;
    }

    @Override
    public void deduct() {

    }

    // ==================================== redisson可重入锁 ===================================
    public void deduct13() {
        RLock lock = redissonClient.getLock(LockConstants.LOCK_KEY);
        lock.lock();
        try {
            // 1. 查询库存信息
            String stock = redisTemplate.opsForValue().get(LockConstants.STOCK_KEY);

            // 2. 判断库存是否充足
            if (StringUtils.isNotEmpty(stock)) {
                long remain = Long.parseLong(stock);
                if (remain > 0) {
                    // 3.扣减库存
                    redisTemplate.opsForValue().set(LockConstants.STOCK_KEY, String.valueOf(remain - 1));
                }
            }
        } finally {
            lock.unlock();
        }
    }

    // ==================================== Redis可重入锁 ===================================
    // 使用手写的redis可重入锁
    public void deduct12() {
        DistributedRedisLock lock = distributedLockFactory.getRedisLock(LockConstants.LOCK_KEY);
        lock.lock();
        try {
            // 1. 查询库存信息
            String stock = redisTemplate.opsForValue().get(LockConstants.STOCK_KEY);

            // 2. 判断库存是否充足
            if (StringUtils.isNotEmpty(stock)) {
                long remain = Long.parseLong(stock);
                if (remain > 0) {
                    // 3.扣减库存
                    redisTemplate.opsForValue().set(LockConstants.STOCK_KEY, String.valueOf(remain - 1));
                }
            }
        } finally {
            lock.unlock();
        }
    }

    // ==================================== Redis分布式锁 ===================================
    // 简单的redis分布式锁，添加uuid作为识别，防止误删
    // 使用 lua 脚本释放锁，保证释放时的原子一致性
    public void deduct11() {
        String uuid = UUID.randomUUID().toString();
        // 加锁 setnx
        while (Boolean.FALSE.equals(this.redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS))) {
            // 重试：循环
            try {
                TimeUnit.MILLISECONDS.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            // this.redisTemplate.expire("lock", 3, TimeUnit.SECONDS);
            // 1. 查询库存信息
            String stock = redisTemplate.opsForValue().get(LockConstants.STOCK_KEY);

            // 2. 判断库存是否充足
            if (StringUtils.isNotEmpty(stock)) {
                long remain = Long.parseLong(stock);
                if (remain > 0) {
                    // 3.扣减库存
                    redisTemplate.opsForValue().set(LockConstants.STOCK_KEY, String.valueOf(remain - 1));
                }
            }
        } finally {
            // 先判断是否自己的锁，再解锁
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                    "then " +
                    "   return redis.call('del', KEYS[1]) " +
                    "else " +
                    "   return 0 " +
                    "end";
            this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), List.of(LockConstants.LOCK_KEY), uuid);
        }
    }

    // 简单的redis分布式锁，添加uuid作为识别，防止误删
    // 由于释放锁判断与操作不是原子性，仍然有可能发生误删
    public void deduct10() {
        String uuid = UUID.randomUUID().toString();
        // 加锁，添加过期时间，防止死锁
        while (Boolean.FALSE.equals(redisTemplate.opsForValue().setIfAbsent(LockConstants.LOCK_KEY, uuid, 30, TimeUnit.SECONDS))) {
            try {
                TimeUnit.MILLISECONDS.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 1.查询库存信息
        try {
            String stock = redisTemplate.opsForValue().get(LockConstants.STOCK_KEY);
            if (StringUtils.isNotEmpty(stock)) {
                // 2.判断库存是否充足
                long remain = Long.parseLong(stock);
                if (remain > 0) {
                    // 3.扣减库存
                    redisTemplate.opsForValue().set(LockConstants.STOCK_KEY, String.valueOf(remain - 1));
                }
            }
        } finally {
            // 释放锁
            // 先判断是否是自己的锁，如果是自己的锁才释放掉，防止误删
            if (StringUtils.equals(redisTemplate.opsForValue().get(LockConstants.LOCK_KEY), uuid)) {
                // 由于这里并没有保证原子性，如果此时有网络波动的话仍然有概率发生误删
                redisTemplate.delete(LockConstants.LOCK_KEY);
            }
        }
    }

    // 简单的redis分布式锁，添加过期时间，防死锁
    public void deduct09() {
        String uuid = UUID.randomUUID().toString();
        // 加锁，添加过期时间，防止死锁
        while (Boolean.FALSE.equals(redisTemplate.opsForValue().setIfAbsent(LockConstants.LOCK_KEY, uuid, 30, TimeUnit.SECONDS))) {
            try {
                TimeUnit.MILLISECONDS.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 1.查询库存信息
        try {
            String stock = redisTemplate.opsForValue().get(LockConstants.STOCK_KEY);
            if (StringUtils.isNotEmpty(stock)) {
                // 2.判断库存是否充足
                long remain = Long.parseLong(stock);
                if (remain > 0) {
                    // 3.扣减库存
                    redisTemplate.opsForValue().set(LockConstants.STOCK_KEY, String.valueOf(remain - 1));
                }
            }
        } finally {
            // 释放锁
            redisTemplate.delete(LockConstants.LOCK_KEY);
        }
    }

    // 简单的redis分布式锁，循环重试
    public void deduct08() {
        // 加锁
        // 重试：循环调用
        while (Boolean.FALSE.equals(redisTemplate.opsForValue().setIfAbsent(LockConstants.LOCK_KEY, "10086"))) {
            try {
                TimeUnit.MILLISECONDS.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 1.查询库存信息
        try {
            String stock = redisTemplate.opsForValue().get(LockConstants.STOCK_KEY);
            if (StringUtils.isNotEmpty(stock)) {
                // 2.判断库存是否充足
                long remain = Long.parseLong(stock);
                if (remain > 0) {
                    // 3.扣减库存
                    redisTemplate.opsForValue().set(LockConstants.STOCK_KEY, String.valueOf(remain - 1));
                }
            }
        } finally {
            // 释放锁
            redisTemplate.delete(LockConstants.LOCK_KEY);
        }
    }


    // 简单的redis分布式锁，递归调用，容易造成栈溢出
    public void deduct07() {
        // 加锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(LockConstants.LOCK_KEY, "10086");
        // 重试：递归调用
        if (Objects.nonNull(lock) && !lock) {
            try {
                TimeUnit.MILLISECONDS.sleep(50);
                this.deduct07();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            // 1.查询库存信息
            try {
                String stock = redisTemplate.opsForValue().get(LockConstants.STOCK_KEY);
                if (StringUtils.isNotEmpty(stock)) {
                    // 2.判断库存是否充足
                    long remain = Long.parseLong(stock);
                    if (remain > 1) {
                        // 3.扣减库存
                        redisTemplate.opsForValue().set(LockConstants.STOCK_KEY, String.valueOf(remain - 1));
                    }
                }
            } finally {
                // 释放锁
                redisTemplate.delete(LockConstants.LOCK_KEY);
            }
        }
    }

    // ==================================== Redis乐观锁 ===================================
    public void deduct06() {
        this.redisTemplate.execute(new SessionCallback<>() {
            @SuppressWarnings("unchecked")
            @Override
            public Object execute(@NotNull RedisOperations operations) throws DataAccessException {
                operations.watch("stock");
                // 1. 查询库存信息
                Object stock = operations.opsForValue().get(LockConstants.STOCK_KEY);
                // 2. 判断库存是否充足
                int st;
                if (stock != null && (st = Integer.parseInt(stock.toString())) > 0) {
                    // 3. 扣减库存
                    operations.multi();
                    operations.opsForValue().set("stock", String.valueOf(--st));
                    List<Object> exec = operations.exec();
                    if (exec.size() == 0) {
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        deduct06();
                    }
                    return exec;
                }
                return null;
            }
        });
    }

    // ==================================== SQL ===================================
    // CAS 乐观锁实现
    public void deduct05() {
        Stock stock = baseMapper.selectById(1L);
        if (stock != null && stock.getCount() > 0) {
            Integer version = stock.getVersion();
            stock.setCount(stock.getCount() - 1);
            // 每次更新版本号+1
            stock.setVersion(version + 1);
            if (baseMapper.update(stock, new LambdaUpdateWrapper<Stock>()
                    .eq(Stock::getId, stock.getId()).eq(Stock::getVersion, version)) == 0) {
                // 更新之前先判断是否是之前查询的那个版本，如果不是重试
                deduct05();
            }
        }
    }

    // select ... for update
    // 注意要开启事务
    @Transactional
    public void deduct04() {
        Stock stock = baseMapper.selectStockForUpdate(1L);
        if (stock != null && stock.getCount() > 0) {
            stock.setCount(stock.getCount() - 1);
        }
        baseMapper.updateById(stock);
    }

    // 在同一个sql中完成修改
    public void deduct03() {
        baseMapper.setStockCount("001", 1);
    }

    // ==================================== JVM ===================================
    public void deduct02() {
        jvmLock.lock();
        try {
            // 先查询库存是否充足
            Stock stock = baseMapper.selectById(1L);
            // 再减库存
            if (stock != null && stock.getCount() > 0) {
                stock.setCount(stock.getCount() - 1);
                baseMapper.updateById(stock);
            }
        } finally {
            jvmLock.unlock();
        }
    }

    // 事务引发的jvm锁失效
    @Transactional
    public void deduct01() {
        jvmLock.lock();
        try {
            // 先查询库存是否充足
            Stock stock = baseMapper.selectById(1L);
            // 再减库存
            if (stock != null && stock.getCount() > 0) {
                stock.setCount(stock.getCount() - 1);
                baseMapper.updateById(stock);
            }
        } finally {
            jvmLock.unlock();
        }
    }

}
